package com.zxhysy.log.annotation.context;

import org.springframework.aop.support.AopUtils;
import org.springframework.context.expression.AnnotatedElementKey;
import org.springframework.context.expression.CachedExpressionEvaluator;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.util.StringUtils;

import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 缓存表达式信息
 * 参考 CacheOperationExpressionEvaluator
 * @author zhangxinhong
 * @since 2022/1/26 16:53
 */
public class LogExpressionEvaluator extends CachedExpressionEvaluator {

    /**
     * 参数解析器
     */
    private final ParameterNameDiscoverer paramNameDiscoverer = new DefaultParameterNameDiscoverer();
    /**
     * 表达式缓存
     */
    private final Map<ExpressionKey, Expression> conditionCache = new ConcurrentHashMap<>(64);
    /**
     * 目标方法缓存
     */
    private final Map<AnnotatedElementKey, Method> targetMethodCache = new ConcurrentHashMap<>(64);
    /**
     * 没有表达式的方法
     */
    private final Set<String> notExpression = Collections.newSetFromMap(new ConcurrentHashMap<>(64));

    /**
     * SpEl 表达式符号
     */
    private static final String CHAT_1 = "#";
    private static final String CHAT_2 = "@";
    private static final String CHAT_3 = "'";
    private static final String CHAT_4 = "$";

    /**
     * 创建上下文信息解析器
     * @param object      目标类对象（controller）
     * @param targetClass 目标类
     * @param method      方法
     * @param args        方法参数
     * @return 上下文信息
     */
    public LogEvaluationContext createEvaluationContext(Object object, Class<?> targetClass, Method method, Object[] args) {
        Method targetMethod = getTargetMethod(targetClass, method);
        return new LogEvaluationContext(object, targetMethod, args, this.paramNameDiscoverer);
    }

    /**
     * 解析信息
     * @param conditionExpression 解析字段
     * @param targetClass         目标类
     * @param method              目标方法
     * @param evalContext         上下文信息
     * @return 解析后的值
     */
    public String condition(String conditionExpression, Class<?> targetClass, Method method, EvaluationContext evalContext) {
        // 判断是否为非SpEl表达式缓存里面
        if (notExpression.contains(conditionExpression)) {
            return conditionExpression;
        } else if (!StringUtils.hasText(conditionExpression)) {
            return addNoExpressionCache(conditionExpression);
        } else if (!conditionExpression.contains(CHAT_1)
                && !conditionExpression.contains(CHAT_2)
                && !conditionExpression.contains(CHAT_3)
                && !conditionExpression.contains(CHAT_4)) {
            // 这里判断是否有符号的
            return addNoExpressionCache(conditionExpression);
        } else {
            // 执行
            AnnotatedElementKey methodKey = new AnnotatedElementKey(method, targetClass);
            return getExpression(this.conditionCache, methodKey, conditionExpression).getValue(evalContext, String.class);
        }
    }

    /**
     * 添加到非表达式内容缓存里面
     * @param conditionExpression 表达式内容
     * @return 原样输出
     */
    private String addNoExpressionCache(String conditionExpression) {
        notExpression.add(conditionExpression);
        return conditionExpression;
    }

    /**
     * 目标方法（可能需要桥接方法）
     * @param targetClass 目标类
     * @param method      调用方法
     * @return 目标方法
     */
    private Method getTargetMethod(Class<?> targetClass, Method method) {
        AnnotatedElementKey methodKey = new AnnotatedElementKey(method, targetClass);
        return this.targetMethodCache.computeIfAbsent(methodKey, k -> AopUtils.getMostSpecificMethod(method, targetClass));
    }

    public ParameterNameDiscoverer getParamNameDiscoverer() {
        return paramNameDiscoverer;
    }
}
